//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------
/*
	TODO: optimize buffer bind/unbind operations
*/

#pragma once

#include "Graphics/GLshell/VertexAttribs.h"
#include "Graphics/GLshell/StateManager.h"

namespace Engine
{
namespace Graphics
{

	//
	// Buffer
	//

	template <typename Binder>
	class Buffer : public BaseObject
	{
	// variables
	private:
		gles::GLuint	_id;
		usize			_size;


	// methods
	public:
		Buffer (EngineSubSystemsRef ess);
		~Buffer ();

		bool Create ();
		void Destroy ();

		bool Allocate (usize size, gles::GLenum usage = gles::GL_STATIC_DRAW);
		bool SetData (const void *data, usize size, gles::GLenum usage = gles::GL_STATIC_DRAW);
		bool SetData (const uni_binbuffer_t &data, gles::GLenum usage = gles::GL_STATIC_DRAW);

		bool SubData (usize offset, const void *data, usize size);
		bool SubData (usize offset, const uni_binbuffer_t &data);

		usize			Size ()		const	{ return _size; }
		gles::GLuint	Id ()		const	{ return _id; }
		gles::GLenum	Target ()	const	{ return Binder::GetTarget(); }
	};



	//
	// Index Buffer Binder
	//

	struct IndexBufferBinder
	{
		static inline
		void Bind (EngineSubSystemsRef ess, gles::GLuint id)
		{
			ess->GetGLStateManager()->PushIndexBuffer( id );
		}

		static inline
		void Unbind (EngineSubSystemsRef ess)
		{
			ess->GetGLStateManager()->PopIndexBuffer();
		}

		static inline
		gles::GLenum GetTarget ()
		{
			return gles::GL_ELEMENT_ARRAY_BUFFER;
		}
	};

	typedef Buffer< IndexBufferBinder >		IndexBuffer;



	//
	// Vertex Buffer Binder
	//

	struct VertexBufferBinder
	{
		static inline
		void Bind (EngineSubSystemsRef ess, gles::GLuint id)
		{
			ess->GetGLStateManager()->PushVertexBuffer( id );
		}

		static inline
		void Unbind (EngineSubSystemsRef ess)
		{
			ess->GetGLStateManager()->PopVertexBuffer();
		}

		static inline
		gles::GLenum GetTarget ()
		{
			return gles::GL_ARRAY_BUFFER;
		}
	};

	typedef Buffer< VertexBufferBinder >	VertexBuffer;

	

	// constructor
	template <typename Binder>
	inline Buffer< Binder >::Buffer (EngineSubSystemsRef ess) :
		BaseObject(ess), _id(0), _size(0)
	{
	}


	// destructor
	template <typename Binder>
	inline Buffer< Binder >::~Buffer ()
	{
		Destroy();
	}


	// Create
	template <typename Binder>
	inline bool Buffer< Binder >::Create ()
	{
		using namespace gles;

		Destroy();

		GL_CALL( glGenBuffers( 1, &_id ) );
		return _id != 0;
	}
	

	// Destroy
	template <typename Binder>
	inline void Buffer< Binder >::Destroy ()
	{
		using namespace gles;

		if ( _id != 0 ) {
			GL_CALL( glDeleteBuffers( 1, &_id ) );
		}
		_id		= 0;
		_size	= 0;
	}


	// Allocate
	template <typename Binder>
	inline bool Buffer< Binder >::Allocate (usize size, gles::GLenum usage)
	{
		using namespace gles;

		if ( _id == 0 )
		{
			CHECK_ERR( Create() );
		}

		Binder::Bind( ESS(), _id );

		_size = size;
		GL_CALL( glBufferData( Target(), _size, null, usage ) );

		Binder::Unbind( ESS() );
		return true;
	}


	// SetData
	template <typename Binder>
	inline bool Buffer< Binder >::SetData (const void *data, usize size, gles::GLenum usage)
	{
		using namespace gles;

		if ( _id == 0 )
		{
			CHECK_ERR( Create() );
		}
		
		Binder::Bind( ESS(), _id );

		_size = size;
		GL_CALL( glBufferData( Target(), _size, data, usage ) );

		Binder::Unbind( ESS() );
		return true;
	}


	// SetData
	template <typename Binder>
	inline bool Buffer< Binder >::SetData (const uni_binbuffer_t &data, gles::GLenum usage)
	{
		return SetData( data.ptr(), data.Size(), usage );
	}


	// SubData
	template <typename Binder>
	inline bool Buffer< Binder >::SubData (usize offset, const void *data, usize size)
	{
		using namespace gles;

		CHECK_ERR( offset + size < _size );
		CHECK_ERR( data != null and size > 0 );
		CHECK_ERR( _id != 0 );

		Binder::Bind( ESS(), _id );

		GL_CALL( glBufferSubData( Target(), offset, size, data ) );

		Binder::Unbind( ESS() );
	}


	// SubData
	template <typename Binder>
	inline bool Buffer< Binder >::SubData (usize offset, const uni_binbuffer_t &data)
	{
		return SubData( offset, data.ptr(), data.Size() );
	}

}	// Graphics
}	// Engine